package sw6.xmlvalidator;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Stack;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

import sw6.xmlvalidator.exceptions.ParserException;
import sw6.xmlvalidator.issues.Error;
import sw6.xmlvalidator.issues.Issue;
import sw6.xmlvalidator.issues.Warning;

/**
 * Validates if ones settings.xml file is valid.
 * The file is valid if its content complies with
 * the rules defined by sw6b.
 * @author Lasse
 */
public class Settings implements ErrorHandler {

	//All different legal types of settings
	private static final String XML_SETTING_TYPE_BOOLEAN 	= "boolean";
	private static final String XML_SETTING_TYPE_DOUBLE 	= "double";
	private static final String XML_SETTING_TYPE_INT 		= "integer";
	private static final String XML_SETTING_TYPE_STRING		= "string";
	private static final String XML_SETTING_TYPE_ENUM		= "enum";
	private static final String XML_SETTING_TYPE_ELEMENT	= "element";
	private static final String XML_SETTING_TYPE_OBJECT		= "object";
	private static final String XML_SETTING_TYPE_STD_OBJECT	= "stdobject";
	private static final String XML_SETTING_TYPE_MENU		= "menu";
	private static final String XML_SETTING_TYPE_ROOT		= "settings";

	//All different legal types of attributes
	private static final String XML_ATTRIBUTE_MENU_TITLE	= "title";
	private static final String XML_ATTRIBUTE_DESC			= "desc";
	private static final String XML_ATTRIBUTE_VAR_NAME 		= "varName";
	private static final String XML_ATTRIBUTE_GUI_ACT_PHONE	= "settingActivity";
	private static final String XML_ATTRIBUTE_GUI_CLASS_PC	= "settingPc";
	private static final String XML_ATTRIBUTE_CLASS_TYPE	= "type";
	private static final String XML_ATTRIBUTE_REAL_NAME		= "realName";
	private static final String XML_ATTRIBUTE_VALUE			= "value";
	private static final String XML_ATTRIBUTE_MIN			= "min";
	private static final String XML_ATTRIBUTE_MAX			= "max";

	//Defines the name of the settings.xml file
	private static final String XML_FILE_NAME				= "settings.xml";

	//Used to define the scope of settings
	private static final String XML_SCOPE_VISIBLE			= "visible";
	private static final String XML_SCOPE_HIDDEN			= "hidden";

	//Used to retain linenumbers after DOM object has been created
	private static final String XML_CUSTOM_LINENUM_PROPERTY = "lineNumber";

	//Used to represent warnings, errors, and exceptions during check of XML file
	private ArrayList<Error> _errors 	 			= new ArrayList<Error>();
	private ArrayList<Warning> _warnings 			= new ArrayList<Warning>();
	private ArrayList<ParserException> _exceptions 	= new ArrayList<ParserException>();

	//The xml file to check
	private File _xmlFile = null;

	/**
	 * Constructor representing the settings.xml file. If the filename
	 * is not equal to "settings.xml" a warning will be given. This is 
	 * done to ensure that the developer remembers a rename before
	 * deployng the xml file to the assets folder of a particular 
	 * SW6 Android App.
	 * @param xmlFile The settings.xml file, representing settings for
	 * an application that is to be used with the sw6.admin system.
	 */
	public Settings(File xmlFile) {
		this._xmlFile = xmlFile;

		//Check the filename
		if(!_xmlFile.getName().equals(XML_FILE_NAME)) {
			_errors.add(new Error(
					_xmlFile.getName() + " is not a valid filename. " +
					"To ensure proper integration with the administration module, " +
					"please rename the file to: " + XML_FILE_NAME, -1));
		}
	}

	/**
	 * Validates the structure and content of the provided XML file.
	 * @return <code>true</code> if the structure of the file conforms
	 * to the rules defined by group sw6b. Otherwise, <code>false</code>
	 * is returned.
	 */
	public boolean hasValidStructure() {
		try {
			//Get a new DOM document factory (to be used to generate a document builder)
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

			//Enable validation of XML file while building the DOM object
			factory.setValidating(true);

			//Get a new DOM document builder
			DocumentBuilder builder = factory.newDocumentBuilder();

			//Define this to be the error handler
			builder.setErrorHandler(this);

			//Parse the xml file 
			System.out.print("Checking XML-file against DTD...");
			builder.parse(_xmlFile);
			System.out.println(" Done.");
			if(!isStructureValid()) {
				printIssues();
				return isStructureValid();
			}

			/*
			 * Please note, at this point I have discarded the returned
			 * dom object from the parse method above. Because, the standard
			 * parse method of a document builder does not retain the line numbers
			 * I only use the the standard parse method to check that the XML file
			 * conforms to the DTD.
			 * 
			 * In the following, I will use another DOM document builder (a custom
			 * parser based on SAX-parsing call-backs) to generate a DOM object
			 * that retains line numbers. I do this to improve the level of 
			 * warnings and errors this tool might report. With line numbers
			 * on every thing, we help the developers to fix their bugs.
			 * 
			 * Why not use the SAX parser for DTD validation you ask? The answer
			 * is that (for some reason I cannot accout for), the DTD validation
			 * of the SAX parser is not as good as the DOM parser, that is: the
			 * error messages are not detailed for instance.
			 */

			//Generate DOM tree
			System.out.print("Generating DOM tree...");
			Document dom = generateDOM(_xmlFile, XML_CUSTOM_LINENUM_PROPERTY);
			System.out.println(" Done.");
			if(!isStructureValid()) {
				printIssues();
				return isStructureValid();
			}

			//Lists of nodes is represented using the DOM object
			Element rootElement 		= dom.getDocumentElement();
			NodeList settings			= dom.getElementsByTagName(XML_SETTING_TYPE_ROOT);
			NodeList booleanSettings 	= rootElement.getElementsByTagName(XML_SETTING_TYPE_BOOLEAN);
			NodeList doubleSettings 	= rootElement.getElementsByTagName(XML_SETTING_TYPE_DOUBLE);
			NodeList intSettings 		= rootElement.getElementsByTagName(XML_SETTING_TYPE_INT);
			NodeList stringSettings 	= rootElement.getElementsByTagName(XML_SETTING_TYPE_STRING);
			NodeList objectSettings  	= rootElement.getElementsByTagName(XML_SETTING_TYPE_OBJECT);
			NodeList stdobjectSettings  = rootElement.getElementsByTagName(XML_SETTING_TYPE_STD_OBJECT);
			NodeList menus  			= rootElement.getElementsByTagName(XML_SETTING_TYPE_MENU);
			NodeList enums  			= rootElement.getElementsByTagName(XML_SETTING_TYPE_ENUM);
			NodeList elements  			= rootElement.getElementsByTagName(XML_SETTING_TYPE_ELEMENT);
			NodeList visibles			= rootElement.getElementsByTagName(XML_SCOPE_VISIBLE);
			NodeList hiddens			= rootElement.getElementsByTagName(XML_SCOPE_HIDDEN);

			//Trim strings, remove leading and trailing whitespace, remove newline tages, and tabs
			System.out.print("Trimming whitespace, newlines, and tabs in attributes and values...");
			NodeList[] nodesToPostProcess = { booleanSettings, 
					doubleSettings, 
					intSettings, 
					stringSettings, 
					objectSettings, 
					stdobjectSettings, 
					menus, 
					enums, 
					elements, 
					settings};
			for(int i = 0; i < nodesToPostProcess.length; i++) {
				postProcessNodes(nodesToPostProcess[i]);
			}
			System.out.println(" Done.");

			System.out.print("Traversing DOM tree checking attributes...");
			//If hidden settings exists, check if the required attributes are set correctly
			if(hiddens.item(0) != null) {
				checkRequiredVarNameAttribute(hiddens.item(0).getChildNodes());
			}

			//If hidden settings exists, check if the required and implied attributes are set correctly
			if(visibles.item(0) != null) {
				checkRequiredVarNameAttribute(visibles.item(0).getChildNodes());
				checkImpliedRealNameAttribute(visibles.item(0).getChildNodes());
			}

			//Check attributes of objects
			checkObjectSettings(objectSettings);
			checkStdObjectSettings(stdobjectSettings);

			//Check attributes of enums
			checkEnumElements(elements);

			//Check attributes of menus
			checkMenus(menus);
			System.out.println(" Done.");

			System.out.print("Checking for duplicated pairs of (variable name, type)...");
			//Check for duplicated pairs of type/variable names
			checkForDuplicatedSettings(booleanSettings,		XML_SETTING_TYPE_BOOLEAN, 	XML_ATTRIBUTE_VAR_NAME);
			checkForDuplicatedSettings(doubleSettings, 		XML_SETTING_TYPE_DOUBLE, 	XML_ATTRIBUTE_VAR_NAME);
			checkForDuplicatedSettings(intSettings, 		XML_SETTING_TYPE_INT, 		XML_ATTRIBUTE_VAR_NAME);
			checkForDuplicatedSettings(stringSettings, 		XML_SETTING_TYPE_STRING, 	XML_ATTRIBUTE_VAR_NAME);
			checkForDuplicatedSettings(objectSettings, 		XML_SETTING_TYPE_OBJECT, 	null);
			checkForDuplicatedSettings(stdobjectSettings, 	XML_SETTING_TYPE_OBJECT, 	null);
			checkForDuplicatedSettings(enums, 				XML_SETTING_TYPE_ENUM, 		XML_ATTRIBUTE_VAR_NAME);
			checkForDuplicatedEnumElements(enums);
			System.out.println(" Done.");

			System.out.print("Checking default values and min / max bounds...");

			/* 
			 * It does not make sense to constraint booleans by min/max bounds.
			 * When min/max bounds is checked for doubles, ints, and strings,
			 * their type of default value is checked too.
			 * As default values is also legal to define for booleans, we do
			 * a check to see if a default boolean value is parsable by calling
			 * the check directly here below.
			 */
			checkBooleanDefaults(booleanSettings);
			checkMinMax(doubleSettings);
			checkMinMax(intSettings);
			checkMinMax(stringSettings);
			System.out.println(" Done.");
		}
		catch (Exception e) {
			System.out.println(" Fail. An exception occured.");
			_exceptions.add(new ParserException(e.toString()));
			
		}
		if(_exceptions.size() > 0 || _errors.size() > 0 || _warnings.size() > 0) {
			printIssues();
		}
		return isStructureValid();
	}

	/**
	 * Obtains the linenumber from a given element in a DOM tree.
	 * The linenumber is bounded to an ELEMENT and not an ATTRIBUTE,
	 * hence, if one is writing a XML tag defined over multiple lines,
	 * the linenumber will be fetched to the last line of the element.
	 * @param element A DOM element.
	 * @return Linenumber of the element in the xml file.
	 */
	private int getLineNumber(Element element) {
		return Integer.parseInt(element.getAttribute(XML_CUSTOM_LINENUM_PROPERTY));
	}

	/**
	 * Checks if a boolean default value (if specified) is parsable.
	 * @param booleans A list of boolean nodes.
	 */
	private void checkBooleanDefaults(NodeList booleans) {
		for(int i = 0; i < booleans.getLength(); i++) {
			Element e = (Element) booleans.item(i);

			//If a default value exists, check if it is parsable
			if(e.getChildNodes().item(0) != null) {
				String value = e.getChildNodes().item(0).getNodeValue(); 
				checkValueType(value, XML_SETTING_TYPE_BOOLEAN, getLineNumber(e));
			}
		}
	}

	/**
	 * After DOM object generation this method can be called to 
	 * trim strings for leading and ending white space, remove
	 * new lines as well as tab indentations.
	 * @param nodes A list of nodes to trim.
	 */
	private void postProcessNodes(NodeList nodes) {
		for(int i = 0; i < nodes.getLength(); i ++) {
			Element e = (Element) nodes.item(i);

			if(e.getNodeName().equalsIgnoreCase(XML_SETTING_TYPE_BOOLEAN)) {
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_VAR_NAME);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_REAL_NAME);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_DESC);
				cleanUpdateAndSetValue(e);
			} else if(e.getNodeName().equalsIgnoreCase(XML_SETTING_TYPE_DOUBLE) ||
					e.getNodeName().equalsIgnoreCase(XML_SETTING_TYPE_INT) ||
					e.getNodeName().equalsIgnoreCase(XML_SETTING_TYPE_STRING)) {
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_VAR_NAME);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_REAL_NAME);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_DESC);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_MIN);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_MAX);
				cleanUpdateAndSetValue(e);
			} else if(e.getNodeName().equalsIgnoreCase(XML_SETTING_TYPE_STD_OBJECT)) {
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_VAR_NAME);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_REAL_NAME);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_DESC);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_CLASS_TYPE);
				cleanUpdateAndSetValue(e);
			} else if(e.getNodeName().equalsIgnoreCase(XML_SETTING_TYPE_OBJECT)) {
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_VAR_NAME);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_REAL_NAME);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_DESC);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_CLASS_TYPE);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_GUI_ACT_PHONE);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_GUI_CLASS_PC);
				cleanUpdateAndSetValue(e);
			} else if(e.getNodeName().equalsIgnoreCase(XML_SETTING_TYPE_MENU)) {
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_MENU_TITLE);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_DESC);
			} else if(e.getNodeName().equalsIgnoreCase(XML_SETTING_TYPE_ENUM)) {
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_VAR_NAME);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_REAL_NAME);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_DESC);
			} else if(e.getNodeName().equalsIgnoreCase(XML_SETTING_TYPE_ELEMENT)) {
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_REAL_NAME);
				cleanUpdateAndSetAttribute(e, XML_ATTRIBUTE_VALUE);
			}
		}
	}

	/**
	 * Trims, removes newlines and tab indentations
	 * from an elements value. If a value does not
	 * exists for the current element, no changes
	 * are made.
	 * @param e Element to clean.
	 */
	private void cleanUpdateAndSetValue(Element e) {
		if(e.getChildNodes().item(0) != null) {
			String value = e.getChildNodes().item(0).getNodeValue();
			value = value.trim();
			value = value.replace("\n", "");
			value = value.replace("\t", "");
			e.getChildNodes().item(0).setNodeValue(value);
		}
	}

	/**
	 * Trims, removes newlines and tab indentations
	 * from an elements attribute. If a particular 
	 * attribute specified by <code>XML_ATTRIBUTE_IDENTIFIER</code> 
	 * does not exists for the current element, no changes are made.
	 * @param e An Element having an attributes which is to be cleaned.
	 * @param XML_ATTRIBUTE_IDENTIFIER Identifier for the attribute to clean.
	 */
	private void cleanUpdateAndSetAttribute(Element e, String XML_ATTRIBUTE_IDENTIFIER) {
		if(e.getAttributeNode(XML_ATTRIBUTE_IDENTIFIER) != null) {
			String value = e.getAttribute(XML_ATTRIBUTE_IDENTIFIER);
			value = value.trim();
			value = value.replace("\n", "");
			value = value.replace("\t", "");
			e.setAttribute(XML_ATTRIBUTE_IDENTIFIER, value);
		}
	}

	/**
	 * Checks the required attributes for objects, where objects are
	 * defined as the custom objects specified in some app.
	 * @param objectSettings A list of object nodes to check.
	 */
	private void checkObjectSettings(NodeList objectSettings) {
		for(int i = 0; i < objectSettings.getLength(); i++) {
			Element setting = (Element) objectSettings.item(i);
			checkRequiredObjectSpecificAttribute(setting, XML_ATTRIBUTE_GUI_ACT_PHONE);
			checkRequiredObjectSpecificAttribute(setting, XML_ATTRIBUTE_GUI_CLASS_PC);
			checkRequiredObjectSpecificAttribute(setting, XML_ATTRIBUTE_CLASS_TYPE);
		}
	}

	/**
	 * Checks the required attributes for stdobjects, where stdobjects are
	 * defined as default objects provided by sw6.lib.types.
	 * @param objectSettings A list of stdobject nodes to check.
	 */
	private void checkStdObjectSettings(NodeList objectSettings) {
		for(int i = 0; i < objectSettings.getLength(); i++) {
			Element setting = (Element) objectSettings.item(i);
			checkRequiredObjectSpecificAttribute(setting, XML_ATTRIBUTE_CLASS_TYPE);
		}
	}

	/**
	 * Checks the required and implied attributes of menus.
	 * @param menus A list of menu nodes to check.
	 */
	private void checkMenus(NodeList menus) {
		for(int i = 0; i < menus.getLength(); i++) {
			Element menu = (Element) menus.item(i);
			checkRequiredAttribute(menu, XML_ATTRIBUTE_MENU_TITLE);
			checkImpliedAttribute(menu, XML_ATTRIBUTE_DESC, false);
		}
	}

	/**
	 * Check the required and implied attributes of an enum element,
	 * as well as if a required default value is specified.
	 * @param enumElements A list of enum elements to check.
	 */
	private void checkEnumElements(NodeList enumElements) {
		for(int i = 0; i < enumElements.getLength(); i++) {
			Element e = (Element) enumElements.item(i);
			checkRequiredAttribute(e, XML_ATTRIBUTE_REAL_NAME);
			checkRequiredAttribute(e, XML_ATTRIBUTE_VALUE);

			//Check if an integer is specified as value for enum element
			String value = e.getAttribute(XML_ATTRIBUTE_VALUE);
			try {
				Integer.parseInt(value);
			} catch(NumberFormatException nfe) {
				reportAttributeIssue(Issue.TYPE.ERROR, XML_ATTRIBUTE_VALUE, getLineNumber(e), "The attribute: \"" + XML_ATTRIBUTE_VALUE + "\" does not contain a parsable integer.");	
			}
		}
	}

	/**
	 * Checks if a required attribute is specified.
	 * @param setting The element to check.
	 * @param XML_ATTRIBUTE_IDENTIFIER The identifier of the attribute to check if exists. 
	 */
	private void checkRequiredAttribute(Element setting, String XML_ATTRIBUTE_IDENTIFIER) {
		String requiredAttribute = setting.getAttribute(XML_ATTRIBUTE_IDENTIFIER);

		//Check if the required attribute is specified
		if(requiredAttribute.length() == 0) {
			reportAttributeIssue(Issue.TYPE.ERROR, XML_ATTRIBUTE_IDENTIFIER, getLineNumber(setting));
		}
	}

	/**
	 * Checks if an implied attribute is defined according to the rules
	 * applied when concerning hidden and visible settings.
	 * 
	 * If a setting is visible, we will throw a warning if the attribute
	 * in question is either not declared or initialized.
	 * 
	 * However, if the setting is hidden, we do not care about the particular
	 * attribute if it has not been declared. However, if it has been declared
	 * we warn the user that he has an uninitialized attribute, as this might 
	 * be an omission.
	 * @param setting The setting having an attribute to check.
	 * @param XML_ATTRIBUTE_IDENTIFIER Identifier of the attribute to check.
	 * @param isVisible Defines if the setting, containing the attribue, is visible or hidden.
	 */
	private void checkImpliedAttribute(Element setting, String XML_ATTRIBUTE_IDENTIFIER, boolean isVisible) {
		checkImpliedAttribute(setting, XML_ATTRIBUTE_IDENTIFIER, isVisible, null);
	}

	/**
	 * Provides the same functionality as: {@link #checkImpliedAttribute(Element, String, boolean)}
	 * However, it adds the opportunity to define a custom message used when reporting warnings.
	 * @param setting The setting having an attribute to check.
	 * @param XML_ATTRIBUTE_IDENTIFIER Identifier of the attribute to check.
	 * @param isVisible Defines if the setting, containing the attribue, is visible or hidden.
	 * @param msg A custom warning message.
	 */
	private void checkImpliedAttribute(Element setting, String XML_ATTRIBUTE_IDENTIFIER, boolean isVisible, String msg) {

		/**
		 * If the implied attribute is hidden, we don't care if it does not has been specified.
		 * However, if it has been specified, the user should be warned that the xml file
		 * contains uninitialized attributes.
		 */
		if(!isVisible) {
			Node impliedAttributeNode = setting.getAttributeNode(XML_ATTRIBUTE_IDENTIFIER);

			if(impliedAttributeNode != null) {
				String impliedAttribute = impliedAttributeNode.getNodeValue();
				if(impliedAttribute.length() == 0) {
					reportAttributeIssue(Issue.TYPE.WARNING, XML_ATTRIBUTE_IDENTIFIER, getLineNumber(setting));
				}
			}
			/**
			 * If the implied attribute is visible, we do care if an implied attribute does not haven been specified.
			 * The reason is, that the implied attribute will be visible to the user when the GUI is auto-generated.
			 * Therefore, if the implied attribute is not specified, we give a warning.
			 */
		} else {
			String impliedAttribute = setting.getAttribute(XML_ATTRIBUTE_IDENTIFIER);

			if(impliedAttribute.length() == 0) {
				if(msg != null) {
					reportAttributeIssue(Issue.TYPE.WARNING, XML_ATTRIBUTE_IDENTIFIER, getLineNumber(setting), msg);
				} else {
					reportAttributeIssue(Issue.TYPE.WARNING, XML_ATTRIBUTE_IDENTIFIER, getLineNumber(setting));
				}
			}
		}
	}

	/**
	 * Checks if some required attribute has been specified for an object.
	 * This method is only intended to be used to check the form of 
	 * paths to classes. A class-path like: <code>sw6.lib.types.Interval</code> is correct,
	 * while a class-path like: <code>sw6.1337app.SomeClass</code> is invalid, as it
	 * starts with a number after a <code>"."</code> delimiter.
	 * @param setting The provided object represented as an Element.
	 * @param XML_ATTRIBUTE_IDENTIFIER 
	 */
	private void checkRequiredObjectSpecificAttribute(Element setting, String XML_ATTRIBUTE_IDENTIFIER) {
		String requiredObjectAttribute = setting.getAttribute(XML_ATTRIBUTE_IDENTIFIER);

		if(requiredObjectAttribute.length() == 0){
			reportAttributeIssue(Issue.TYPE.ERROR, XML_ATTRIBUTE_IDENTIFIER, getLineNumber(setting));
			return;
		}

		String[] attributeSplit = requiredObjectAttribute.split("\\.");
		for(int i = 0; i < attributeSplit.length; i++ ) {
			if(attributeSplit[i].length() == 0 || !Character.isLetter(attributeSplit[i].toCharArray()[0])) {
				_errors.add(new Error("Invalid name assigned to attribute: \"" + 
						XML_ATTRIBUTE_IDENTIFIER + "\". The name should follow standard Java syntax for canonical class-names.", 
						getLineNumber(setting)));
				break;
			}
		}
	}

	/**
	 * Checks if the required varName attribute has been
	 * specified for a list of settings.
	 * @param settings A list of settings to check.
	 */
	private void checkRequiredVarNameAttribute(NodeList settings) {

		for(int i = 0; i < settings.getLength(); i++) {
			Element setting = (Element) settings.item(i);
			if(!setting.getNodeName().equalsIgnoreCase(XML_SETTING_TYPE_MENU)) {
				checkRequiredAttribute(setting, XML_ATTRIBUTE_VAR_NAME);
			} else {
				checkRequiredVarNameAttribute(setting.getChildNodes());
			}
		}
	}

	/**
	 * Check if the implied realName attribute has been
	 * specified for a list of settings.
	 * @param settings A list of settings to check.
	 */
	private void checkImpliedRealNameAttribute(NodeList settings) {

		for(int i = 0; i < settings.getLength(); i++) {
			Element setting = (Element) settings.item(i);
			String settingNodeName = setting.getNodeName();

			if(!settingNodeName.equalsIgnoreCase(XML_SETTING_TYPE_MENU)) {
				String msgEnd = "is empty. It might be a good idea to initialize this attribute, " +
				"as it will be visible to the user.";
				checkImpliedAttribute(setting, XML_ATTRIBUTE_REAL_NAME, true,	
						"The attribute: \"" + XML_ATTRIBUTE_REAL_NAME + "\" " + msgEnd);
			} else if(settingNodeName.equalsIgnoreCase(XML_SETTING_TYPE_MENU)){
				checkImpliedRealNameAttribute(setting.getChildNodes()); 
			}
		}
	}

	/**
	 * Checks wheter the default value for a setting of type
	 * <code>integer</code>, <code>double</code>, or <code>string</code>
	 * complies with the boundary constraints set by min and or max.
	 * The method checks if <code>min</code> is less than or equal to 
	 * <code>max</code> and vice versa. Further, the method also checks 
	 * if any default value set for these types of settings has been 
	 * specified with a type that is parsable for the type of setting in 
	 * question.
	 * @param settings The list of settings to check.
	 */
	private void checkMinMax(NodeList settings) {
		
		//For each setting do
		for(int i = 0; i < settings.getLength(); i++) {
			Element setting = (Element) settings.item(i);
			String settingNodeName = setting.getNodeName();

			// If setting is of type double, check default value and min/max
			if(settingNodeName.equalsIgnoreCase(XML_SETTING_TYPE_DOUBLE)) {
				boolean hasValidDoubleValue = false;
				double valueDouble 	= 0.0;
				String min			= setting.getAttribute(XML_ATTRIBUTE_MIN);
				String max			= setting.getAttribute(XML_ATTRIBUTE_MAX);
				Node doubleNode		= setting.getChildNodes().item(0);
				if(doubleNode != null && 
						checkValueType(doubleNode.getNodeValue(), XML_SETTING_TYPE_DOUBLE, getLineNumber(setting)) == true) {
					valueDouble = Double.parseDouble(doubleNode.getNodeValue());
					hasValidDoubleValue = true;
				}
				
				/* If both min and max bounds are set, check their type
				 * and if the default value is in the interval.
				 */
				if(min.length() > 0 && max.length() > 0) {
					
					//Check the type of the min and max bounds
					if(checkValueType(min, XML_SETTING_TYPE_DOUBLE, getLineNumber(setting), false) == true &&
							checkValueType(max, XML_SETTING_TYPE_DOUBLE, getLineNumber(setting), true) == true) {

						//Both types were parsable as doubles, so - parse them!
						double minDouble = Double.parseDouble(min);
						double maxDouble = Double.parseDouble(max);

						//Check the min max relation
						checkMinMaxRelation(minDouble, maxDouble, getLineNumber(setting));

						//If the default value has a valid type, check if the value complies with the boundaries
						if(hasValidDoubleValue) {
							checkMinBound(valueDouble, minDouble, getLineNumber(setting));
							checkMaxBound(valueDouble, maxDouble, getLineNumber(setting));
						}
					}
				} else if(min.length() > 0) {
					if(checkValueType(min, XML_SETTING_TYPE_DOUBLE, getLineNumber(setting)) == true) {
						double minDouble = Double.parseDouble(min);
						
						if(hasValidDoubleValue) {
							checkMinBound(valueDouble, minDouble, getLineNumber(setting));
						}
					}
				}  else if(max.length() > 0) {
					if(checkValueType(max, XML_SETTING_TYPE_DOUBLE, getLineNumber(setting)) == true) {
						double maxDouble = Double.parseDouble(max);
						
						if(hasValidDoubleValue) {
							checkMaxBound(valueDouble, maxDouble, getLineNumber(setting));
						}
					}
				}
			} else if(settingNodeName.equalsIgnoreCase(XML_SETTING_TYPE_INT)) {
				boolean hasValidIntValue = false;
				int valueInt 		= 0;
				String min			= setting.getAttribute(XML_ATTRIBUTE_MIN);
				String max			= setting.getAttribute(XML_ATTRIBUTE_MAX);
				Node intNode		= setting.getChildNodes().item(0);
				if(intNode != null && 
						checkValueType(intNode.getNodeValue(), XML_SETTING_TYPE_INT, getLineNumber(setting)) == true) {
					valueInt = Integer.parseInt(intNode.getNodeValue());
					hasValidIntValue = true;
				}
				if(min.length() > 0 && max.length() > 0) {
					if(checkValueType(min, XML_SETTING_TYPE_INT, getLineNumber(setting), false) == true &&
							checkValueType(max, XML_SETTING_TYPE_INT, getLineNumber(setting), true) == true) {

						int minInt = Integer.parseInt(min);
						int maxInt = Integer.parseInt(max);

						checkMinMaxRelation(minInt, maxInt, getLineNumber(setting));

						if(hasValidIntValue) {
							checkMinBound(valueInt, minInt, getLineNumber(setting));
							checkMaxBound(valueInt, maxInt, getLineNumber(setting));
						}
					}
				} else if(min.length() > 0) {
					if(checkValueType(min, XML_SETTING_TYPE_INT, getLineNumber(setting)) == true) {
						int minInt = Integer.parseInt(min);
						
						if(hasValidIntValue) {
							checkMinBound(valueInt, minInt, getLineNumber(setting));
						}
					}
				}  else if(max.length() > 0) {
					if(checkValueType(max, XML_SETTING_TYPE_INT, getLineNumber(setting)) == true) {
						int maxInt = Integer.parseInt(max);
						
						if(hasValidIntValue) {
							checkMaxBound(valueInt, maxInt, getLineNumber(setting));
						}
					}
				}
			}
			//Same as above. Only difference: based on string length
			else if(settingNodeName.equalsIgnoreCase(XML_SETTING_TYPE_STRING)) {
				boolean hasValidStringValue = false;
				int valueStringLen 	= 0;
				String min			= setting.getAttribute(XML_ATTRIBUTE_MIN);
				String max			= setting.getAttribute(XML_ATTRIBUTE_MAX);
				String valueString 	= "";
				String middleStringMin = "";
				String middleStringMax = "";
				Node stringNode		= setting.getChildNodes().item(0);
				if(stringNode != null) {
					valueString 	= stringNode.getNodeValue();
					valueStringLen 	= valueString.length();
					middleStringMin = ", with value: \"" + valueString + "\" of length: " + valueStringLen + ", exceeds the string length bound set by min: ";
					middleStringMax = ", with value: \"" + valueString + "\" of length: " + valueStringLen + ", exceeds the string length bound set by max: ";
					hasValidStringValue = true;
				}
				if(min.length() > 0 && max.length() > 0) {
					if(checkValueType(min, XML_SETTING_TYPE_INT, getLineNumber(setting), false) == true &&
							checkValueType(max, XML_SETTING_TYPE_INT, getLineNumber(setting), true) == true) {

						int minStringLen = Integer.parseInt(min);
						int maxStringLen = Integer.parseInt(max);

						checkMinMaxRelation(minStringLen, maxStringLen, getLineNumber(setting));

						if(hasValidStringValue) {
							checkMinBound(valueStringLen, minStringLen, getLineNumber(setting), "The variable of type: " + valueString.getClass().getSimpleName().toLowerCase() + middleStringMin + minStringLen);
							checkMaxBound(valueStringLen, maxStringLen, getLineNumber(setting), "The variable of type: " + valueString.getClass().getSimpleName().toLowerCase() + middleStringMax + maxStringLen);
						}
					}
				} else if(min.length() > 0) {
					if(checkValueType(min, XML_SETTING_TYPE_INT, getLineNumber(setting)) == true) {
						int minStringLen = Integer.parseInt(min);
						
						if(hasValidStringValue) {
							checkMinBound(valueStringLen, minStringLen, getLineNumber(setting), "The variable of type: " + valueString.getClass().getSimpleName().toLowerCase() + middleStringMin + minStringLen);
						}
					}
				}  else if(max.length() > 0) {
					if(checkValueType(max, XML_SETTING_TYPE_INT, getLineNumber(setting)) == true) {
						int maxStringLen = Integer.parseInt(max);
						
						if(hasValidStringValue) {
							checkMaxBound(valueStringLen, maxStringLen, getLineNumber(setting), "The variable of type: " + valueString.getClass().getSimpleName().toLowerCase() + middleStringMin + maxStringLen);
						}
					}
				}
			}
		}
	}

	/**
	 * Checks if a value is greater than its min bound. 
	 * The method adds an error to the errorlist if min 
	 * is greater than the provided value.
	 * @param <T> A comparable type, e.g. <code>Double</code>
	 * @param value A comparable value.
	 * @param mibBound The minimum value allowed for the provided value.
	 * @param lineNumber The linenumber of where the value is placed
	 * in the xml file.
	 * @param errMsg A custom errormessage to use, if an error must 
	 * be added to the errorlist.
	 */
	private <T extends Comparable<T>> void checkMinBound(T value, T minBound, int lineNumber, String errMsg) {
		if(value.compareTo(minBound) >= 0) {
			return;
		} else {
			_errors.add(new Error(errMsg, lineNumber));
		}
	}
	
	/**
	 * Checks if a value is greater than its min bound. 
	 * The method adds an error to the errorlist if min 
	 * is greater than the provided value.
	 * @param <T> A comparable type, e.g. <code>Double</code>
	 * @param value A comparable value.
	 * @param mibBound The minimum value allowed for the provided value.
	 * @param lineNumber The linenumber of where the value is placed
	 * in the xml file.
	 */
	private <T extends Comparable<T>> void checkMinBound(T value, T minBound, int lineNumber) {
		checkMinBound(value, minBound, lineNumber, "The variable of type: " + value.getClass().getSimpleName().toLowerCase() + ", with value: " + value + ", exceeds the bounds set by min: " + minBound);
	}

	/**
	 * Provides similar functionality as: {@link #checkMinBound(T, T, int, String)}
	 * However, this method compares a value against its maximum bound.
	 * @param <T> A comparable type, e.g. <code>Double</code>
	 * @param value A comparable value.
	 * @param maxBound The maximum value allowed for the provided value.
	 * @param lineNumber The linenumber of where the value is placed
	 * in the xml file.
	 * @param errMsg A custom errormessage to use, if an error must 
	 * be added to the errorlist.
	 */
	private <T extends Comparable<T>> void checkMaxBound(T value, T maxBound, int lineNumber, String errMsg) {
		if(value.compareTo(maxBound) <= 0) {
			return;
		} else {
			_errors.add(new Error(errMsg, lineNumber));
		}
	}
	
	/**
	 * Provides similar functionality as: {@link #checkMinBound(T, T, int)}
	 * However, this method compares a value against its maximum bound.
	 * @param <T> A comparable type, e.g. <code>Double</code>
	 * @param value A comparable value.
	 * @param maxBound The maximum value allowed for the provided value.
	 * @param lineNumber The linenumber of where the value is placed
	 * in the xml file.
	 */
	private <T extends Comparable<T>> void checkMaxBound(T value, T maxBound, int lineNumber) {
		checkMaxBound(value, maxBound, lineNumber, "The variable of type: " + value.getClass().getSimpleName().toLowerCase() + ", with value: " + value + ", exceeds the bounds set by max: " + maxBound);
	}

	/**
	 * Compares two min and max bounds to check if min is less
	 * than or equal to max. If this is not the case, an error
	 * is added to the error list.
	 * @param <T> A comparable type, e.g. <code>Double</code>
	 * @param min The minimum bound.
	 * @param max The maximum bound.
	 * @param lineNumber The linenumber belonging to the xml
	 * element which have these min/max boundaries.
	 */
	private <T extends Comparable<T>> void checkMinMaxRelation(T min, T max, int lineNumber) {
		if(min.compareTo(max) > 0) {
			_errors.add(new Error("min: " + min + ", is greater than max: " + max + ", which is not a valid boundary constraint.", lineNumber));
		} else {
			return;
		}
	}

	/**
	 * Checks if a value is of the type specified by the <code>type</code> identifier.
	 * The method is often used when one wants to check if the type of a parsed
	 * node value has a certain type. This method overrides other implementations
	 * and is in particular used to check the type of the values specified for min
	 * and max attributes. The method is an override to provide specific error messages.
	 * @param value Some value, e.g. an integer represented as a <code>String</code>.
	 * @param type The expected type represented as a <code>String</code>. 
	 * Refer to the internal String constants defining the available types.
	 * @param lineNumber The linenumber of the element containing the value and type.
	 * @param isMax Defines if one is checking the type of the value written in 
	 * <code>min</code> or <code>max</code>. 
	 * @return <code>true</code> if the value is of the predicted type. <code>false</code>
	 * is returned otherwise.
	 */
	private boolean checkValueType(String value, String type, int lineNumber, boolean isMax) {

		String attributeToUseInMsg = "";
		if(isMax) {
			attributeToUseInMsg = "max";
		} else {
			attributeToUseInMsg = "min";
		}

		String errMsg = "\"" + value + "\" is not a parsable default value for attribute " + attributeToUseInMsg + " for the setting of type: " + type;
		return checkValueType(value, type, lineNumber, errMsg);
	}

	/**
	 * Checks if a value is of the type specified by the <code>type</code> identifier.
	 * The method is often used when one wants to check if the type of a parsed
	 * node value has a certain type.
	 * @param value Some value, e.g. an integer represented as a <code>String</code>.
	 * @param type The expected type represented as a <code>String</code>. 
	 * Refer to the internal String constants defining the available types.
	 * @param lineNumber The linenumber of the element containing the value and type.
	 * @return <code>true</code> if the value is of the predicted type. <code>false</code>
	 * is returned otherwise.
	 */
	private boolean checkValueType(String value, String type, int lineNumber) {
		String errMsg = "\"" + value + "\" is not a parsable default value for a setting of type: " + type;
		return checkValueType(value, type, lineNumber, errMsg);
	}

	/**
	 * Checks if a value is of the type specified by the <code>type</code> identifier.
	 * The method is often used when one wants to check if the type of a parsed
	 * node value has a certain type. This method overrides other implementations
	 * and is in particular used to check the type of the values specified for min
	 * and max attributes. The method is an override to provide specific error messages.
	 * @param value Some value, e.g. an integer represented as a <code>String</code>.
	 * @param type The expected type represented as a <code>String</code>. 
	 * Refer to the internal String constants defining the available types.
	 * @param lineNumber The linenumber of the element containing the value and type.
	 * @param errMsg A custom error message that will be added to an <code>Error</code>
	 * object and put in the list of found errors.
	 * @return <code>true</code> if the value is of the predicted type. <code>false</code>
	 * is returned otherwise.
	 */
	private boolean checkValueType(String value, String type, int lineNumber, String errMsg) {
		if(type.equalsIgnoreCase(XML_SETTING_TYPE_INT)) {
			try {
				Integer.parseInt(value);
				return true;
			} catch(NumberFormatException e) {
				_errors.add(new Error(errMsg, lineNumber));
			}
		} else if(type.equalsIgnoreCase(XML_SETTING_TYPE_DOUBLE)) {
			try {
				Double.parseDouble(value);
				return true;
			} catch(NumberFormatException e) {
				_errors.add(new Error(errMsg, lineNumber));
			}
		} else {
			if(!value.equalsIgnoreCase("true") && !value.equals("false")) {
				_errors.add(new Error(errMsg, lineNumber));
			} else {
				return true;
			}
		}
		return false;
	}

	/**
	 * Checks for duplicated enum elements. That is, if multiple elements 
	 * for the same enum has the same value. As this pr. definition of the 
	 * enum type is not allowed, an error will be raised if multiple enum 
	 * elements with the same value is found.
	 * @param enums A list of enums to check for elements
	 * with the same value.
	 */
	private void checkForDuplicatedEnumElements(NodeList enums){
		for(int i = 0; i < enums.getLength(); i++) {
			Element e = (Element) enums.item(i);
			checkForDuplicatedSettings(e.getChildNodes(), XML_SETTING_TYPE_ELEMENT, XML_ATTRIBUTE_REAL_NAME);
			checkForDuplicatedSettings(e.getChildNodes(), XML_SETTING_TYPE_ELEMENT, XML_ATTRIBUTE_VALUE);
		}
	}

	/**
	 * Checks for duplicated (attribute,value) pairs for some setting
	 * and some attribute. The method is typically used for checking 
	 * if multiple variables with the same name and type has been 
	 * specified. Further, it could also be used to check for duplicated 
	 * values of enum elements.
	 * Based on the designed database schemas, sw6b do not allow mutiple 
	 * occurences of variables with the same type and combination of 
	 * appName and varName. This means that variables with the same name
	 * is allowed as long as their types are different.
	 * @param settings The list of settings to check for dulicates.
	 * @param XML_SETTING_IDENTIFIER Identifies the type of XML setting.
	 * @param XML_ATTRIBUTE_UNIQUE_KEY_IDENTIFER Identifies the attribute
	 * for which one wants to check for duplicated values.
	 */
	private void checkForDuplicatedSettings(NodeList settings, String XML_SETTING_IDENTIFIER, String XML_ATTRIBUTE_UNIQUE_KEY_IDENTIFER) {

		if(XML_SETTING_IDENTIFIER.equalsIgnoreCase(XML_SETTING_TYPE_OBJECT) || XML_SETTING_IDENTIFIER.equalsIgnoreCase(XML_SETTING_TYPE_STD_OBJECT)) {
			HashMap<String, String> hm = new HashMap<String, String>();
			for(int i = 0; i < settings.getLength(); i++) {
				Element setting = (Element) settings.item(i);
				String varName = setting.getAttribute(XML_ATTRIBUTE_VAR_NAME);
				String type = setting.getAttribute(XML_ATTRIBUTE_CLASS_TYPE);
				if(hm.get(varName) != null && hm.get(varName).equals(type)) {
					String msg = "Multiple occurences of an object with same varName and type is not allowed.";
					_errors.add(new Error(msg, getLineNumber(setting)));
				}
				hm.put(varName, type);
			}
		} else {
			ArrayList<String> varNames = new ArrayList<String>();
			for(int i = 0; i < settings.getLength(); i++) {
				Element setting = (Element) settings.item(i);
				String varName 	= setting.getAttribute(XML_ATTRIBUTE_UNIQUE_KEY_IDENTIFER);
				if(varNames.contains(varName)) {
					
					String msg = "Multiple occurences of a setting with type: \"" + XML_SETTING_IDENTIFIER + "\", having the same \"" + XML_ATTRIBUTE_UNIQUE_KEY_IDENTIFER + "\" attribute is";
					
					if(XML_SETTING_IDENTIFIER.equalsIgnoreCase(XML_SETTING_TYPE_ELEMENT)) {
						msg = msg.concat(" not allowed inside this enum.");
					} else {
						msg = msg.concat(" not allowed.");
					}
					
					_errors.add(new Error(msg, getLineNumber(setting)));
				}
				varNames.add(varName);
			}
		}
	}

	/**
	 * This method is developed by: http://eyalsch.wordpress.com/2010/11/30/xml-dom-2/
	 * Thanks and credits to: Eyal Schneider
	 * <br/><br/>
	 * The method parses the settings.xml and verifies its structure using our provided
	 * DTD file. The method returns a DOM representation of the XML file, which we
	 * use to make furthers checks on the content of the XML file.
	 * <br/><br/>
	 * The reason why we use this method by Eyal Schneider is, that we want to keep the
	 * linenumber information on each element in the DOM tree - also when the parsing
	 * has finished. We use the linenumber in our own content-verification to report
	 * useful error messages. The method written by Eyal Schneider uses the SAX parser
	 * to parse XML, and as SAX is based on call back methods, Eyal uses this to edit
	 * the element of the DOM tree before a node is added to the three. In this way, 
	 * linenumbers may be added, and that information will be available in the resulting
	 * DOM object tree.
	 * @param is Some XML file to parse and build the DOM representation upon.
	 * @param lineNumAttribName Identifier of the new linenumber attribute to be
	 * added.
	 * @return A DOM object tree representing the parsed XML file.
	 * @throws IOException
	 * @throws SAXException
	 */
	private Document generateDOM(File is, final String lineNumAttribName) throws IOException, SAXException {
		final Document doc;
		SAXParser parser;
		try {
			SAXParserFactory factory = SAXParserFactory.newInstance();
			parser = factory.newSAXParser();
			DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
			docBuilderFactory.setIgnoringElementContentWhitespace(true);
			DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
			doc = docBuilder.newDocument();
		} catch(ParserConfigurationException e){
			throw new RuntimeException("Can't create SAX parser / DOM builder.", e);
		}

		final Stack<Element> elementStack = new Stack<Element>();
		final StringBuilder textBuffer = new StringBuilder();
		DefaultHandler handler = new DefaultHandler() {
			private Locator locator;

			@Override
			public void setDocumentLocator(Locator locator) {
				this.locator = locator; //Save the locator, so that it can be used later for line tracking when traversing nodes.
			}

			@Override
			public void startElement(String uri, String localName, String qName, org.xml.sax.Attributes attributes) throws SAXException {
				addTextIfNeeded();
				Element el = doc.createElement(qName);
				for(int i = 0;i < attributes.getLength(); i++)
					el.setAttribute(attributes.getQName(i), attributes.getValue(i));
				el.setAttribute(lineNumAttribName, String.valueOf(locator.getLineNumber()));
				elementStack.push(el);
			}

			@Override
			public void endElement(String uri, String localName, String qName){
				addTextIfNeeded();
				Element closedEl = elementStack.pop();
				if (elementStack.isEmpty()) { // Is this the root element?
					doc.appendChild(closedEl);
				} else {
					Element parentEl = elementStack.peek();
					parentEl.appendChild(closedEl);
				}
			}

			@Override
			public void characters (char ch[], int start, int length) throws SAXException {
				textBuffer.append(ch, start, length);
			}

			// Outputs text accumulated under the current node
			private void addTextIfNeeded() {
				if (textBuffer.length() > 0) {
					Element el = elementStack.peek();
					Node textNode = doc.createTextNode(textBuffer.toString());
					el.appendChild(textNode);
					textBuffer.delete(0, textBuffer.length());
				}
			}
		};
		try{
			parser.parse(is, handler);
		}catch(SAXParseException e) {
			_exceptions.add(new ParserException("An exception occured during DOM generation, details: " + e.toString()));
		}
		return doc;
	}

	//Fatal errors (implented as a part of the ErrorHandler for the DOM parser)
	public void fatalError(SAXParseException e) throws SAXException { 
		error(e);
	}

	//Validation errors (implented as a part of the ErrorHandler for the DOM parser)
	public void error(SAXParseException e) throws SAXParseException {
		String msg = e.getMessage();
		
		// pattern for DTD errors: invalid element used
		String headElemErr 		= "Element type";
		String tailElemErr 		= "must be declared.";
		String patternElemErr	= headElemErr + ".*" + tailElemErr;
		
		// pattern for DTD errors: invalid attribute used
		String headAttrErr 		= "Attribute";
		String tailAttrErr 		= "must be declared";
		String patternAttrErr 	= headAttrErr + ".*" + tailAttrErr + ".*";
		
		// check if the patterns can be matched
		if(msg.matches(patternElemErr)) {
			String newElemErrHead 	= "The element";
			String newElemErrTail 	= "is not declared in the DTD grammar. You cannot use it here.";
			String newElemErrBody 	= msg.substring(headElemErr.length()+1, (msg.length()-tailElemErr.length())-1);
			msg = newElemErrHead + " " + newElemErrBody + " " + newElemErrTail;
		} else if(msg.matches(patternAttrErr)) {
			String newAttrErrHead 	= "The attribute ";
			String newAttrErrBody 	= " is not declared in the DTD grammar for the element: ";
			String newAttrErrTail 	= " You cannot use it here.";
			String attribute	= "";
			String element		= "";
			String[] splitMsg 	= msg.split("\"");
			if(splitMsg.length == 5) {
				attribute = splitMsg[1];
				element = splitMsg[3];
				msg = newAttrErrHead + "\"" + attribute + "\"" + newAttrErrBody + "\"" + element + "\"" + newAttrErrTail;
			}
		}
		_errors.add(new Error(msg, e.getLineNumber()));		
	}

	//Parsing warnings (implented as a part of the ErrorHandler for the DOM parser)
	public void warning(SAXParseException e) throws SAXParseException{
		_warnings.add(new Warning(e.getMessage(), e.getLineNumber()));
	}

	/**
	 * Reports that some attribute is empty.
	 * @param issueType Type of issue.
	 * @param attribute Attribute identifier.
	 * @param lineNumber Linenumber of the element containing the empty attribute.
	 * @see sw6.xmlvalidator.issues.Issue
	 */
	private void reportAttributeIssue(Issue.TYPE issueType, String attribute, int lineNumber) {
		String msg = "The attribute: \"" + attribute + "\" is empty.";
		reportAttributeIssue(issueType, attribute, lineNumber, msg);
	}

	/**
	 * Overloaded implementation of: {@link #reportAttributeIssue(Issue.TYPE, String, int)}. This
	 * method further takes a custom error message as argument.
	 * @param issueType Type of issue.
	 * @param attribute Attribute identifier.
	 * @param lineNumber Linenumber of the element containing the empty attribute.
	 * @param msg Some error message to add to the type of issue which is being reported.
	 * @see sw6.xmlvalidator.issues.Issue
	 */
	private void reportAttributeIssue(Issue.TYPE issueType, String attribute, int lineNumber, String msg) {
		switch(issueType) {
		case ERROR :
			_errors.add(new Error(msg, lineNumber));
			break;
		case WARNING :
			_warnings.add(new Warning(msg, lineNumber));
			break;
		}
	}

	/**
	 * Prints a list of issues registered.
	 * If a list is empty, the list header is not printed.
	 */
	private void printIssues() {
		Collections.sort(_errors);
		Collections.sort(_warnings);

		if(_exceptions.size() > 0) {
			System.err.println("EXCEPTIONS (" + this._exceptions.size() + ")");
			for (ParserException exception : this._exceptions) {
				System.err.println(exception);
			}
		}
		
		if(_errors.size() > 0) {
			System.err.println("ERRORS (" + this._errors.size() + ")");
			for (Error error : this._errors) {
				System.err.println(error);
			}
		}

		if(_warnings.size() > 0) {
			System.err.println("WARNINGS (" + this._warnings.size() + ")");
			for (Warning warning : this._warnings) {
				System.err.println(warning);
			}
		}
	}

	/**
	 * Checks if the current state of the file is valid.
	 * @return <code>true</code> if no errors or exceptions have 
	 * been registered. Otherwise, <code>false</code> is returned.
	 */
	private boolean isStructureValid() {
		return (_errors.size() > 0 || _exceptions.size() > 0) ? false : true;
	}
}
